\chapter{开发环境搭建}

先楫半导体的HPM6750的片子使用的是RISC-V的内核。
开发的时候可以使用GCC的编译器或者使用``Segger Embedded Studio for RISC-V''。
这里我们根据先楫半导体GitHub仓库的指导来配置一个docker开发环境。相关链接如下：
\begin{itemize}
    \item \href{https://github.com/hpmicro}{先楫半导体的GitHub仓库}--\url{https://github.com/hpmicro}
    \begin{itemize}
        \item \href{https://github.com/hpmicro/hpm_sdk}{SDK仓库}--\url{https://github.com/hpmicro/hpm_sdk}
        \item \href{https://github.com/hpmicro/riscv-gnu-toolchain}{针对HPM6750定制的RISCV32-GCC编译器}--\url{https://github.com/hpmicro/riscv-gnu-toolchain}
        \item \href{https://github.com/hpmicro/riscv-openocd}{针对HPM6750定制的openocd调试软件仓库}---\url{https://github.com/hpmicro/riscv-openocd}
    \end{itemize}
    \item \href{https://www.segger.com/downloads/embedded-studio/}{Segger Embedded Studio 下载链接}\footnote{有ARM版本和RISC-V版本，选择下载RISC-V版本即可}---\url{https://www.segger.com/downloads/embedded-studio/}
\end{itemize}

接下来根据SDK中的ReadMe文件在docker中配置开发环境。\footnote{ReadMe文件可以直接在\href{https://github.com/hpmicro/hpm_sdk}{SDK仓库链接中查看}}
\begin{enumerate}
    \item 首先拉取一个基础的docker镜像，并启动竟然其命令行。其操作命令如下：
    \begin{minted}{bash}
        # 拉取ubuntu:20.04的基础镜像
        docker pull ubuntu:20.04 
        # 使用ubuntu:20.04镜像创建一个容器，并启动bash    
        docker run -it --name hpm_guide ubuntu:20.04 bash 
    \end{minted}
    \item 根据SDK中ReadMe的指导先安装基本的依赖：
    \begin{minted}{bash}
        # 更新缓存
        apt update -y
        # 由于安装python的时候需要选择时区，
        # 这里提前设置TZ环境变量可以不需要在安装的时候选择时区，
        # 在使用dockfile生成镜像的时候这样设置是非常有意义的,
        # tzdata安装时需要DEBIAN_FRONTEND来设置其使用非交互式的方式来设置时区。
        export TZ=Asia/Shanghai 
        export DEBIAN_FRONTEND=noninteractive
        ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
        # 安装必要的软件依赖
        apt install build-essential cmake ninja-build \
         libc6-i386 libc6-i386-cross libstdc++6-i386-cross \
         python3 python3-pip -y
    \end{minted}
    \item riscv32-unknown-elf-gcc编译工具链。
    点开\href{https://github.com/hpmicro/riscv-gnu-toolchain}{针对HPM6750定制的RISCV32-GCC编译器}这个链接，
    然后点击`tags'进入版本页面，然后点击`Release',在Assert中找到`riscv32-unknown-elf-newlib-multilib\_2022.05.15\_linux.tar.gz'文件下载下来。
    操作过程如图~\ref{fig:dlgcc1}，~\ref{fig:dlgcc2}所示。
    \begin{figure}[htbp]
        \centering
        \includegraphics[width=0.7\columnwidth]{image/downloadgcc1.png}
        \caption{RISCV32-GCC下载页面}\label{fig:dlgcc1}
    \end{figure}
    \begin{figure}[htbp]
        \centering
        \includegraphics[height=0.7\columnwidth]{image/downloadgcc2.pdf}
        \caption{RISCV32-GCC下载页面}\label{fig:dlgcc2}
    \end{figure}
    \item 安装riscv32-unknown-elf-gcc工具链。
    由于需要在docker中安装软件，所以需要将压缩包拷贝到docker环境中，在主机命令行中执行下面命令
    \footnote{\textcolor{orange}{``/home/otter/Downloads/riscv32-unknown-elf-newlib-multilib\_2022.05.15\_linux.tar.gz''需要替换为所下载的文件的绝对路径。}}:
    \begin{minted}{bash}
        docker cp \
            /home/otter/Downloads/riscv32-unknown-elf-newlib-multilib_2022.05.15_linux.tar.gz \
            hpm_guide:/tmp
    \end{minted}
    然后在docker环境中将压缩包解压到/opt/目录。
    \begin{minted}{bash}
        cd /opt
        tar -xf riscv32-unknown-elf-newlib-multilib_2022.05.15_linux.tar.gz\
           -C /opt/
    \end{minted}
    解压完成后可以得到`/opt/riscv32-unknown-elf-newlib-multilib'目录，将这个目录下的`bin'目录添加到系统
    环境变量PAHT中，然后执行`riscv32-unknown-elf-gcc --version'命令可以查看riscv32-gcc的版本信息。代码清单如下：
    \begin{minted}{bash}
        export PATH=$PATH:/opt/riscv32-unknown-elf-newlib-multilib/bin
        riscv32-unknown-elf-gcc --version
    \end{minted}
    如果上面的代码可以正常获取到riscv32-gcc的版本信息，这说明工具链安装成功。
    让软件变量的修改在下次启动的时候依然有效，可以在/etc/profile文件最后新建一行或者在/etc/profile.d文件夹中新建一个`.sh'文件，然后加入这行代码
    \begin{minted}{bash}
        export PATH=$PATH:/opt/riscv32-unknown-elf-newlib-multilib/bin
    \end{minted}
    \begin{itemize}
        \item 在/etc/profile最后一行添加代码命令：
        \begin{minted}{bash}
            # 执行这条命令前，建议先备份/etc/profile文件
            echo 'export PATH=$PATH:/opt/riscv32-unknown-elf-newlib-multilib/bin'\
                >> /etc/profile
        \end{minted}
        \item 在/etc/profile.d中新建文件添加代码的命令：
        \begin{minted}{bash}
            #执行这条命令前请确保文件/etc/profile.d/riscv32-gcc.sh不存在
            echo 'export PATH=$PATH:/opt/riscv32-unknown-elf-newlib-multilib/bin' \
                > /etc/profile.d/riscv32-gcc.sh
        \end{minted}
        安装完了riscv32-gcc后，需要清理一下压缩包和apt的缓存，然后退出docker容器：
        \begin{minted}{bash}
            # 删除压缩包
            rm /tmp/riscv32-unknown-elf-newlib-multilib_2022.05.15_linux.tar.gz 
            # 清理apt的缓存
            rm -rf rm -rf /var/lib/apt/lists/*
            exit
        \end{minted}
        然后在主机环境中执行下面的命令将容器保存为一个新的镜像。
        \begin{minted}{bash}
            # hpm_guide 是容器的名称， ubuntu是镜像的tag
            docker commit hpm_guide ubuntu:hpm_dev
        \end{minted}
    \end{itemize}
    \item 编译和安装openocd调试软件\footnote{由于作者还不清楚如何在Docker环境下使用USB设备，下面的操作都是在主机环境中完成的}。
    首先从\href{https://github.com/hpmicro/riscv-openocd}{GitHub}上拉取openocd的代码，切出hpm\_xpi\_v0.1.0版本代码，然后根据ReadMe的提示编译并安装openocd%
    \footnote{由于作者的机器上已经安装好了环境依赖，所以省略了依赖安装的步骤，后期补齐}。
    \begin{minted}{bash}
        # 克隆riscv-openocd代码到本地
        git clone https://github.com/hpmicro/riscv-openocd.git
        # 进入riscv-openocd 目录
        cd riscv-openocd
        # 执行openocd指导中的编译命令
        ./bootstrap
        ./configure
        make
        sudo make install 
    \end{minted}
    \item 编译SDK中的第一个程序：
    \begin{enumerate}
        \item 主机环境下将代码克隆下来：
        \begin{minted}{bash}
            git clone https://github.com/hpmicro/hpm_sdk.git
        \end{minted}
        \item 之前保存的镜像启动一个容器，并将刚才克隆下来的源码目录挂载到容器中。
        \begin{minted}{bash}
            # /home/otter/source/hpm_sdk是主机目录，这里要求使用绝对路径
            # /tmp/hpm_sdk 是容器路径
            docker run -v /home/otter/source/hpm_sdk:/tmp/hpm_sdk \
                --name hpm_guide -it ubuntu:hpm_dev bash
        \end{minted}
        \item 容器环境下，进入/tmp/hpm\_sdk目录,然后手动配置一下环境变量 HPM\_SDK\_BASE。
        \begin{minted}{bash}
            cd /tmp/hpm_sdk
            export HPM_SDK_BASE=`pwd`
            export GNURISCV_TOOLCHAIN_PATH=/opt/riscv32-unknown-elf-newlib-multilib
        \end{minted}
        \item 安装python库\footnote{这一步应该在之前安装软件的时候就安装好的。}
        \begin{minted}{bash}
            apt update -y
            pip3 install -r "$HPM_SDK_BASE/scripts/requirements.txt"
        \end{minted}
        \item 进入sample/hello\_world目录，使用cmake生成ninja编译规则，并编译程序
        \begin{minted}{bash}
            cd /tmp/hpm_sdk/samples/hello_world
            cmake -GNinja -Bbuild -DBOARD=hpm6750evk .
            ninja -C build
        \end{minted}
        下面的代码是上面的命令执行的日志情况。
        \begin{minted}{text}
            root@dd9c50ddff91:/tmp/hpm_sdk/samples/hello_world# cmake -GNinja -Bbuild -DBOARD=hpm6750evk .
            -- Application: /tmp/hpm_sdk/samples/hello_world
            -- Board: hpm6750evk
            -- Found toolchain: gnu (/opt/riscv32-unknown-elf-newlib-multilib)
            -- The C compiler identification is GNU 11.1.0
            -- The CXX compiler identification is GNU 11.1.0
            -- The ASM compiler identification is GNU
            -- Found assembler: /opt/riscv32-unknown-elf-newlib-multilib/bin/riscv32-unknown-elf-gcc
            --  Segger: openocd can not be located
            -- Segger linker script: /tmp/hpm_sdk/soc/HPM6750/toolchains/segger/ram.icf
            -- Segger device name: HPM6750xVMx
            --  Segger: no debugger configuration is generated, due to missing openocd information. Please configure manually in Segger Embedded Studio
            -- Segger Embedded Studio Project: /tmp/hpm_sdk/samples/hello_world/build/segger_embedded_studio/hello_world.emProject
            -- Configuring done
            -- Generating done
            -- Build files have been written to: /tmp/hpm_sdk/samples/hello_world/build
            root@dd9c50ddff91:/tmp/hpm_sdk/samples/hello_world# ninja -C build
            ninja: Entering directory `build'
            [98/98] Linking C executable output/demo.elf
            Memory region         Used Size  Region Size  %age Used
                        ILM:          0 GB       256 KB      0.00%
                        DLM:          0 GB       256 KB      0.00%
                CORE0_LM_SLV:       46928 B       512 KB      8.95%
                CORE1_LM_SLV:          0 GB       512 KB      0.00%
                    SDRAM:          0 GB        32 MB      0.00%
                    AXI_SRAM:       33272 B       768 KB      4.23%
            AXI_SRAM_NONCACHEABLE:          0 GB       256 KB      0.00%
        \end{minted}
        到此，编译环境已经搭建完成。
    \end{enumerate}
    \item 切换到主机环境准备使用gdb+openocd调试代码。
    \begin{minted}{bash}
        # 设置环境变量，不然openocd找不到配置文件，请将后边的绝对路径修改为实际SDK相对应的路径
        export OPENOCD_SCRIPTS=/home/otter/source/hpm_sdk/boards/openocd
        openocd -f probes/jlink.cfg -f soc/hpm6750-single-core.cfg -f boards/hpm6750evk.cfg
    \end{minted}
    openocd输出的日志信息如下：
    \begin{minted}{text}
        Open On-Chip Debugger 0.11.0+dev (2022-08-04-23:19)
        Licensed under GNU GPL v2
        For bug reports, read
                http://openocd.org/doc/doxygen/bugs.html
        srst_only separate srst_gates_jtag srst_open_drain connect_deassert_srst

        Info : Listening on port 6666 for tcl connections
        Info : Listening on port 4444 for telnet connections
        Info : J-Link V9 compiled May  7 2021 16:26:12
        Info : Hardware version: 9.60
        Info : VTarget = 3.304 V
        Info : clock speed 10000 kHz
        Info : JTAG tap: hpm6750.cpu tap/device found: 0x1000563d (mfg: 0x31e (Andes Technology Corporation), part: 0x0005, ver: 0x1)
        Info : [hpm6750.cpu0] datacount=4 progbufsize=8
        Info : Examined RISC-V core; found 2 harts
        Info :  hart 0: XLEN=32, misa=0x4094112d
        [hpm6750.cpu0] Target successfully examined.
        Info : starting gdb server for hpm6750.cpu0 on 3333
        Info : Listening on port 3333 for gdb connections
    \end{minted}
    新开一个主机命令行终端，切换到编译生成的elf文件所在的目录，然后启动gdb。
    \begin{minted}{bash}
        cd /home/otter/source/hpm_sdk/samples/hello_world/build
        riscv32-unknown-elf-gdb output/demo.elf -ex "target extended-remote localhost:3333"
    \end{minted}
    执行完上面的命令后，gdb已经成功启动，使用下面命令调试代码：
    \begin{minted}{bash}
        (gdb)monitor reset halt
        (gdb)load
        (gdb)b main
        (gdb)c
    \end{minted}
    上面的gdb的命令含义依次是：\\
    \mintinline{bash}{moniotr reset halt} 复位并停止MCU。\\
    \mintinline{bash}{load} 加载程序到MCU。\\
    \mintinline{bash}{b main} 在``main''函数处设置断点。\\
    \mintinline{bash}{c} 继续运行。由于在``main''函数处设置了断点，运行到``main''函数是会暂停
\end{enumerate}

\begin{problemset}[练习]
    \item 网络上搜索链接gdb的远程代理调试器时一般使用的是\mintinline{bash}{target remote localhost:3333},
    而这里使用的是\mintinline{bash}{target extended-remote localhost:3333}。
    如果使用\mintinline{bash}{target remote localhost:3333}会有什么样的结果呢？
\end{problemset}

\begin{remark}
    安装必要软件依赖的时候有点麻烦，如果不设置环境变量的话，python3和tzdata的安装会要求使用交互式的方式来设置时区。
    这对于无人值守的安装(使用Dockerfile的方式生成镜像)来说是非常讨厌的。需要使用export设置环境变量来解决这个问题。
    如果安装其它软件软件的过程中，出现了需要交互式输入时，请搜索如何避开这些手动输入。
\end{remark}